﻿using System;
using Plugins.Core;
using Plugins.UserControls;
using StockAccessors;
using StockModel;

namespace Plugins.Common
{
	public class OperationManager
	{
		#region Attributes

		private readonly IOrderExecutor _orderExecutor;
		private readonly StockParametersSelectorData _parameters;

        private bool _inverted;
        private OperationExecutionResult _lastExecutionResult;

		#endregion

		#region Properties

		public string StockSymbol { get; set; }
		public Operation CurrentOperation { get; private set; }
        public Quotation CurrentQuotation { private get; set; }

        public bool Inverted
        {
            get
            {
                return _inverted;
            }
            set
            {
                if (_inverted != value)
                {
                    if (this.CurrentOperation != null)
                    {
                        throw new InvalidOperationException("Cannot change the invert status in the middle of an operation");
                    }
                    else
                    {
                        _inverted = value;
                    }
                }
            }
        }

		#endregion

		#region Events

		public event EventHandler<OperationExecutedEventArgs> OperationExecuted;
		public event EventHandler<RealtimeNotificationEventArgs> RealtimeNotification;

		#endregion

		#region Public Methods

		public OperationManager(StockParametersSelectorData parameters, IOrderExecutor orderExecutor)
		{
			_parameters = parameters;
			_orderExecutor = orderExecutor;
		}

		public void OpenLongPossition(int volume)
		{
			if (string.IsNullOrEmpty(this.StockSymbol))
			{
				throw new InvalidOperationException("The Stock Symbol must be set before opening possitions");
			}
			else if (this.CurrentOperation != null)
			{
				throw new InvalidOperationException("Having more than one operation at once is not supported");
			}

            this.CurrentOperation = new Operation(_parameters.ValueMultiplier);

            if (_inverted)
            {
                _lastExecutionResult = _orderExecutor.ExecuteMarketOrder(this.StockSymbol, volume, true);
                
                // Fake the current operation with the expected values as the object is going to be used by the decission engine 
				this.CurrentOperation.BuyDate = this.CurrentQuotation.Date;
				this.CurrentOperation.BuyPrice = this.CurrentQuotation.Sellers[0].Value;
            }
            else
            {
                _lastExecutionResult = _orderExecutor.ExecuteMarketOrder(this.StockSymbol, volume, false);

				this.CurrentOperation.BuyDate = _lastExecutionResult.Date;
				this.CurrentOperation.BuyPrice = _lastExecutionResult.Value;
            }

			this.CurrentOperation.Volume = volume;
			this.CurrentOperation.Fee = this.CalculateFee();

            this.NotifyRealtimeOperation(true);
		}

		public void OpenShortPossition(int volume)
		{
			if (string.IsNullOrEmpty(this.StockSymbol))
			{
				throw new InvalidOperationException("The Stock Symbol must be set before opening possitions");
			}
			else if (this.CurrentOperation != null)
			{
				throw new InvalidOperationException("Having more than one operation at once is not supported");
			}

			this.CurrentOperation = new Operation(_parameters.ValueMultiplier);

            if (_inverted)
            {
                _lastExecutionResult = _orderExecutor.ExecuteMarketOrder(this.StockSymbol, volume, false);

                // Fake the current operation with the expected values as the object is going to be used by the decission engine 
				this.CurrentOperation.SellDate = this.CurrentQuotation.Date;
				this.CurrentOperation.SellPrice = this.CurrentQuotation.Buyers[0].Value;
            }
            else
            {
                _lastExecutionResult = _orderExecutor.ExecuteMarketOrder(this.StockSymbol, volume, true);

				this.CurrentOperation.SellDate = _lastExecutionResult.Date;
				this.CurrentOperation.SellPrice = _lastExecutionResult.Value;
            }

			this.CurrentOperation.Volume = volume;
			this.CurrentOperation.Fee = this.CalculateFee();
			
            this.NotifyRealtimeOperation(false);
		}

		public void CloseLongPossition()
		{
			if (this.CurrentOperation == null)
			{
				throw new InvalidOperationException("Can not close an operation before opening one");
			}
			else if (this.CurrentOperation.BuyPrice == 0)
			{
				throw new InvalidOperationException("Triying to close a long possition when a short was opened");
			}

            if (_inverted)
            {
                // Rebuild the current operation with the right values as the object is going to be used only for reporting uses from now on
                this.CurrentOperation.SellDate = _lastExecutionResult.Date;
                this.CurrentOperation.SellPrice = _lastExecutionResult.Value; 

                _lastExecutionResult = _orderExecutor.ExecuteMarketOrder(this.StockSymbol, this.CurrentOperation.Volume, false);

                this.CurrentOperation.BuyDate = _lastExecutionResult.Date;
                this.CurrentOperation.BuyPrice = _lastExecutionResult.Value; 
            }
            else
            {
                _lastExecutionResult = _orderExecutor.ExecuteMarketOrder(this.StockSymbol, this.CurrentOperation.Volume, true);

                this.CurrentOperation.SellDate = _lastExecutionResult.Date;
                this.CurrentOperation.SellPrice = _lastExecutionResult.Value; 
            }

            this.CurrentOperation.Fee += this.CalculateFee();
			this.CurrentOperation.AmountRequired = this.CurrentOperation.Fee + Math.Max(this.CurrentOperation.SellPrice, this.CurrentOperation.BuyPrice) * _parameters.PercentageReserve / 100 * this.CurrentOperation.Volume + _parameters.FixedReserve * this.CurrentOperation.Volume - (this.CurrentOperation.Result < 0 ? this.CurrentOperation.Result : 0); // Only count losses as benefits does not makes us need less money to start

			this.OnOperationExecuted(this.CurrentOperation);
            this.NotifyRealtimeOperation(true);
			this.CurrentOperation = null;
		}

		public void CloseShortPossition()
		{
			if (this.CurrentOperation == null)
			{
				throw new InvalidOperationException("Can not close an operation before opening one");
			}
			else if (this.CurrentOperation.SellPrice == 0)
			{
				throw new InvalidOperationException("Trying to close a short possition when a long was opened");
			}

            if (_inverted)
            {
                // Rebuild the current operation with the right values as the object is going to be used only for reporting uses from now on
                this.CurrentOperation.BuyDate = _lastExecutionResult.Date;
                this.CurrentOperation.BuyPrice = _lastExecutionResult.Value;

                _lastExecutionResult = _orderExecutor.ExecuteMarketOrder(this.StockSymbol, this.CurrentOperation.Volume, true);

                this.CurrentOperation.SellDate = _lastExecutionResult.Date;
                this.CurrentOperation.SellPrice = _lastExecutionResult.Value;
            }
            else
            {
                _lastExecutionResult = _orderExecutor.ExecuteMarketOrder(this.StockSymbol, this.CurrentOperation.Volume, false);

                this.CurrentOperation.BuyDate = _lastExecutionResult.Date;
                this.CurrentOperation.BuyPrice = _lastExecutionResult.Value;
            }

            this.CurrentOperation.Fee += this.CalculateFee();
			this.CurrentOperation.AmountRequired = this.CurrentOperation.Fee + Math.Max(this.CurrentOperation.SellPrice, this.CurrentOperation.BuyPrice) * _parameters.PercentageReserve / 100 * this.CurrentOperation.Volume + _parameters.FixedReserve * this.CurrentOperation.Volume - (this.CurrentOperation.Result < 0 ? this.CurrentOperation.Result : 0);

			this.OnOperationExecuted(this.CurrentOperation);
            this.NotifyRealtimeOperation(false);
			this.CurrentOperation = null;
		}

		protected virtual void OnOperationExecuted(Operation operation)
		{
			if (this.OperationExecuted != null)
			{
				this.OperationExecuted(this,new OperationExecutedEventArgs(operation));
			}
		}

		protected virtual void OnRealtimeNotification(string message)
		{
			if (this.RealtimeNotification != null)
			{
				this.RealtimeNotification(this, new RealtimeNotificationEventArgs(message));
			}
		}

		#endregion

		#region Private Methods

        private void NotifyRealtimeOperation(bool buySell)
        {
            this.OnRealtimeNotification(string.Format("{0}. Price: {1} - Volume {2} - Time: {3} - Inverted: {4}", buySell ? "Sell done" : "Buy done", _lastExecutionResult.Value, this.CurrentOperation.Volume, _lastExecutionResult.Date, _inverted));
        }

        private decimal CalculateFee()
        {
			if (this.CurrentOperation.Volume == 0)
			{
				throw new InvalidOperationException("The volume must be set before calculating the fees");
			}
            return _parameters.FixedFeePerItem * this.CurrentOperation.Volume + (_lastExecutionResult.Value * this.CurrentOperation.Volume * _parameters.PercentageFee / 100) + _parameters.FixedFeePerOperation;
        }

		#endregion
	}
}
